package com.zjgsu.nftplatform.service.impl;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.NumberUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.zjgsu.nftplatform.client.BlockchainClient;
import com.zjgsu.nftplatform.entity.*;
import com.zjgsu.nftplatform.mapper.*;
import com.zjgsu.nftplatform.pojo.*;
import com.zjgsu.nftplatform.service.LoginService;
import com.zjgsu.nftplatform.service.UserService;
import com.zjgsu.nftplatform.util.JWTUtil;
import com.zjgsu.nftplatform.util.UserIdUtil;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.AuthorizationException;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class UserServiceImpl implements UserService {

    private final JWTUtil jwtUtil;
    private final UserMapper userMapper;
    private final UserWareMapper userWareMapper;
    private final FollowMapper followMapper;
    private final StarMapper starMapper;
    private final WareMapper wareMapper;
    private final NftMapper nftMapper;
    private final LoginService loginService;
    private final DealMapper dealMapper;
    private final BlockchainClient blockchainClient;

    public UserServiceImpl(JWTUtil jwtUtil, UserMapper userMapper, UserWareMapper userWareMapper, FollowMapper followMapper, StarMapper starMapper, WareMapper wareMapper, NftMapper nftMapper, LoginService loginService, DealMapper dealMapper, BlockchainClient blockchainClient) {
        this.jwtUtil = jwtUtil;
        this.userMapper = userMapper;
        this.userWareMapper = userWareMapper;
        this.followMapper = followMapper;
        this.starMapper = starMapper;
        this.wareMapper = wareMapper;
        this.nftMapper = nftMapper;
        this.loginService = loginService;
        this.dealMapper = dealMapper;
        this.blockchainClient = blockchainClient;
    }

    @Override
    @SuppressWarnings({"unchecked", "rawtypes"})
    public UserInfo userInfo(Long userId) {
        if (userId == null) {
            userId = getUserId();
        }
        User user = userMapper.selectOne(new QueryWrapper<User>().eq("id", userId));
        Set<String> roles = loginService.getRolesByUserId(userId);
        Long followGoods = followMapper.selectCount(new QueryWrapper<Follow>().eq("follower_id", userId).eq("follow_type", 2));
        Long followCreator = followMapper.selectCount(new QueryWrapper<Follow>().eq("follower_id", userId).eq("follow_type", 1));
        Long followMe = followMapper.selectCount(new QueryWrapper<Follow>().eq("followed_id", userId).eq("follow_type", 1));
        Long liked = starMapper.selectCount(new QueryWrapper<Star>().eq("type", 1).inSql("object_id", "SELECT ware_id FROM user_ware WHERE user_id = " + userId + " AND `status` = 1"));
        Long myArtwork = userWareMapper.selectCount(new QueryWrapper<UserWare>().eq("user_id", userId).eq("status", 1));
        List<Long> artworkId = (List<Long>) (List) userWareMapper.selectObjs(new QueryWrapper<UserWare>().eq("user_id", userId).select("ware_id"));
        Assert.notNull(user, "无效的id");

        List<BigDecimal> incomes = (List<BigDecimal>) (List) dealMapper.selectObjs(new QueryWrapper<Deal>().eq("seller_id", userId).select("price"));
        BigDecimal income = new BigDecimal("0.00");
        for (BigDecimal bigDecimal : incomes) {
            income = NumberUtil.add(income, bigDecimal);
        }

        return UserInfo.builder()
                .userId(user.getId().toString())
                .img(user.getImg())
                .name(user.getName())
                .signature(user.getSignature())
                .roles(roles)
                .certification(user.getCertification())
                .followGoods(followGoods)
                .followCreator(followCreator)
                .followMe(followMe)
                .liked(liked)
                .phone(user.getPhone())
                .myArtwork(myArtwork)
                .artworkId(artworkId)
                .assets(user.getAssets())
                .income(income)
                .address(user.getAddress())
                .build();
    }

    @Override
    public Integer settingInfo(String img, String name, String signature, String phone, Boolean certification) {

        return userMapper.update(null, new UpdateWrapper<User>()
                        .eq("id", getUserId())
                        .set("img", img)
                        .set("name", name)
                        .set("signature", signature)
                        .set("phone", phone)
//                修改密码未做
                        .set("certification", certification)
        );
    }

    @Override
    public NftView nftView(Long nftId) {
        Nft nft = nftMapper.selectOne(new QueryWrapper<Nft>().eq("id", nftId));
        return new NftView(nft);
    }

    @Override
    public String nftCreate(CreateNftInfo info) {
        Long userId = getUserId();
        User user = userMapper.selectById(userId);

        String txHash;
        Result result;
        if (info.getParents() == null || info.getParents().size() == 0) {
            result = blockchainClient.nftCreate(info.getFileHash(), user.getAddress(), user.getPasswd());
            Assert.isTrue(result.getCode() == ResultCode.SUCCESS.getCode(), "请求创建nft失败，请检查参数是否错误，您的账户是否有以太币余额");
        } else {
            result = blockchainClient.nftAdapt(JSON.toJSONString(info.getParents()), info.getFileHash(), user.getAddress(), user.getPasswd());
            Assert.isTrue(result.getCode() == ResultCode.SUCCESS.getCode(), "请求改编nft失败，请检查参数是否错误，您的账户是否有以太币余额");
        }
        txHash = (String) result.getData();

        result = blockchainClient.getIdByFileHash(info.getFileHash());
        Assert.isTrue(result.getCode() == ResultCode.SUCCESS.getCode(), "请求nft id失败");
        Long nftId = Long.valueOf((String) result.getData());

        Nft nft = new Nft();
        nft.setId(nftId);
        nft.setImg(info.getImg());
        nft.setName(info.getName());
        nft.setDescription(info.getDescription());
        nft.setKind(info.getKind());
        nft.setAttribute(info.getAttributes());
        nft.setFileHash(info.getFileHash());
        nft.setUserId(userId);
        nft.setUsername(user.getName());
        nft.setViews(0);
        nft.setOwnerId(userId);
        nft.setOwnerName(user.getName());
        nft.setStatus(2);
        nft.setChainAddress(txHash);
        Assert.isTrue(nftMapper.insert(nft) > 0, "插入nft失败");

        Calendar now = Calendar.getInstance();
        String username = userMapper.selectById(userId).getName();

        Ware ware = Ware.builder()
                .uploaderId(UserIdUtil.getUserId())
                .uploaderName(username)
                .nftId(nftId)
                .startTime(now.getTime())
                .status(info.getKind().equals("成品") ? 1 : 0)
                .build();
        Assert.isTrue(wareMapper.insert(ware) > 0, "商品上架失败");
        Assert.isTrue(userWareMapper.insert(UserWare.builder()
                .userId(UserIdUtil.getUserId())
                .wareId(ware.getId())
                .nftId(nftId)
                .status(1)
                .build()
        ) > 0, "商品上架失败");

        return nftId.toString();
    }

    @Override
    public void nftUpdate(Long nftId, String img, String name, String description, String attribute) {
//        Result result = nftClient.add(file);
//        Assert.isTrue(result.getCode() == ResultCode.SUCCESS.getCode(), "无法请求blockchain模块");
//        String fileHash = (String) result.getData();
        Assert.notNull(nftMapper.selectById(nftId), "查找不到nft：" + nftId);
        Assert.isTrue(nftMapper.updateById(Nft.builder()
                .id(nftId)
                .userId(getUserId())
                .img(img)
                .name(name)
                .description(description)
                .attribute(attribute)
                .build()
        ) > 0, "更新nft信息失败：" + nftId);
    }

    @Override
    @SuppressWarnings({"unchecked", "rawtypes"})
    public PageInfo<ArtFollow> artFollow(Long pageNum, Long pageSize) {
        Long userId = getUserId();
        PageHelper.startPage(pageNum.intValue(), pageSize.intValue());

        List<Long> wareId = (List<Long>) (List) followMapper.selectObjs(new QueryWrapper<Follow>().eq("follower_id", userId).eq("follow_type", 2).select("followed_id"));
        if (wareId.size() == 0) {
            return PageInfo.of(new ArrayList<>());
        }
        List<Ware> wares = wareMapper.selectList(new QueryWrapper<Ware>().in("id", wareId));
        List<Long> nftIds = wares.stream().map(Ware::getNftId).collect(Collectors.toList());
        List<Nft> nfts = nftMapper.selectList(new QueryWrapper<Nft>().in("id", nftIds));
        nfts.sort(Comparator.comparingInt(n -> nftIds.indexOf(n.getId())));

        List<ArtFollow> artFollows = new ArrayList<>();
        for (int i = 0; i < wares.size(); i++) {
            artFollows.add(ArtFollow.builder()
                    .id(nfts.get(i).getId())
                    .img(nfts.get(i).getImg())
                    .name(nfts.get(i).getName())
                    .attribute(nfts.get(i).getAttribute())
                    .uploaderId(wares.get(i).getUploaderId())
                    .uploaderName(wares.get(i).getUploaderName())
                    .lastPrice(wares.get(i).getLastPrice())
                    .price(wares.get(i).getPrice())
                    .build());
        }

        return new PageInfo<>(artFollows);
    }

    @Override
    @SuppressWarnings({"unchecked", "rawtypes"})
    public PageInfo<CreatorFollow> creatorFollow(Long pageNum, Long pageSize) {
        Long userId = getUserId();
        PageHelper.startPage(pageNum.intValue(), pageSize.intValue());
//        List<CreatorFollow> follower = (List)followMapper.selectList(new QueryWrapper<Follow>().eq("follower_id",userId).eq("follow_type",1));
        List<Long> followedIds = (List<Long>) (List) followMapper.selectObjs(new QueryWrapper<Follow>().eq("follower_id", userId).eq("follow_type", 1).select("followed_id"));
        if (followedIds.size() == 0) {
            return PageInfo.of(new ArrayList<>());
        }
        List<User> user = userMapper.selectList(new QueryWrapper<User>().in("id", followedIds));

        List<CreatorFollow> follower = new ArrayList<>();
        for (User value : user) {
            follower.add(CreatorFollow.builder()
                    .img(value.getImg())
                    .name(value.getName())
                    .signature(value.getSignature())
                    .build());
        }

        return new PageInfo<>(follower);
    }

    @Override
    @SuppressWarnings({"rawtypes", "unchecked"})
    public PageInfo<Fan> fans(Long pageNum, Long pageSize) {
        Long userId = getUserId();
        PageHelper.startPage(pageNum.intValue(), pageSize.intValue());

        List<Long> followerId = (List<Long>) (List) followMapper.selectObjs(new QueryWrapper<Follow>().eq("followed_id", userId).eq("follow_type", 1).select("follower_id"));
        if (followerId.size() == 0) {
            return PageInfo.of(new ArrayList<>());
        }
        List<User> user = userMapper.selectList(new QueryWrapper<User>().in("id", followerId));

        List<Fan> fans = new ArrayList<>();
        for (User value : user) {
            fans.add(Fan.builder()
                    .id(value.getId())
                    .img(value.getImg())
                    .name(value.getName())
                    .signature(value.getSignature())
                    .build());
        }

        return new PageInfo<>(fans);
    }

    @Override
    public void createUser(CreateUserInfo info) {
        String name = info.getName();
        Assert.isNull(userMapper.selectOne(new QueryWrapper<User>().eq("name", name)), "该用户名已经被使用");

        Result result = blockchainClient.accountNew(info.getPasswd());
        Assert.isTrue(result.getCode() == ResultCode.SUCCESS.getCode(), "创建以太坊账户失败");
        String userAddress = (String) result.getData();

        User newUser = User.builder()
                .name(info.getName())
                .passwd(info.getPasswd())
                .phone(info.getPhone())
                .img(info.getImg())
                .signature(info.getSignature())
                .certification(false)
                .assets(BigDecimal.ZERO)
                .address(userAddress)
                .build();
        Assert.isTrue(userMapper.insert(newUser
        ) > 0, "新建用户失败");

        loginService.insertRole(newUser.getId(), "common");
    }

    @Override
    public void certificate() {
        long userId = UserIdUtil.getUserId();
        try {
            SecurityUtils.getSubject().checkRole("certificated");
            throw new RuntimeException("你已经认证过了");
        } catch (AuthorizationException e) {
            loginService.insertRole(userId, "certificated");
        }
    }

    @Override
    public PageInfo<NftView> getNft(Long userId, Integer pageNum, Integer pageSize) {
        if (userId == null) {
            userId = getUserId();
        }
        Page<NftView> page = PageHelper.startPage(pageNum, pageSize);
        List<NftView> nftViews = nftMapper.selectByMap(MapUtil.of("user_id", userId)).stream().map(NftView::new).collect(Collectors.toList());
        PageInfo<NftView> pageInfo = PageInfo.of(page);
        pageInfo.setList(nftViews);
        return pageInfo;
    }

    @Override
    public PageInfo<ArtsManage> userGoods(Long userId, Integer pageNum, Integer pageSize) {
//        if (userId == null) {
//            userId = getUserId();
//        }
//
//        List<Nft> nfts = nftMapper.selectList(new QueryWrapper<Nft>().eq("user_id", userId));
//        if (nfts.size() == 0) {
//            return new ArrayList<>();
//        }
//        List<Long> nftIds1 = nfts.stream().map(Nft::getId).collect(Collectors.toList());
//        List<Ware> ware = wareMapper.selectList(new QueryWrapper<Ware>().in("nft_id", nftIds1));
//        List<Long> wareIds1 = ware.stream().map(Ware::getId).collect(Collectors.toList());
//        Map<Long, List<Ware>> nftWares = new HashMap<>();
//        ware.forEach(w -> {
//            nftWares.putIfAbsent(w.getNftId(), new ArrayList<>());
//            nftWares.get(w.getNftId()).add(w);
//        });
//
//        List<ArtsManage> arts = new ArrayList<>();
//        nfts.forEach(n -> {
//            List<Ware> wares = nftWares.get(n.getId());
//            if (wares != null && wares.size() > 0) {
//                wares.forEach(w -> arts.add(ArtsManage.builder()
//                        .name(n.getName())
//                        .id(w.getId().toString())
//                        .uploaderId(n.getUserId().toString())
//                        .uploaderName(n.getUsername())
//                        .attribute(n.getAttribute())
//                        .right(w.getRight())
//                        .status(w.getStatus())
//                        .saleMethod(w.getSaleMethod())
//                        .price(w.getPrice())
//                        .resell(w.getResell())
//                        .edition(w.getEdition())
//                        .build()));
//            } else {
//                arts.add(ArtsManage.builder()
//                        .name(n.getName())
//                        .uploaderId(n.getUserId().toString())
//                        .uploaderName(n.getUsername())
//                        .attribute(n.getAttribute())
//                        .build());
//            }
//        });
//
//        List<Object> wareIds2 = userWareMapper.selectObjs(new QueryWrapper<UserWare>()
//                .eq("user_id", userId)
//                .notIn(wareIds1.size() > 0, "ware_id", wareIds1)
//                .select("ware_id"));
//        List<Ware> wares = wareMapper.selectList(new QueryWrapper<Ware>().in(wareIds2 != null && wareIds2.size() > 0, "id", wareIds2));
//        List<Long> nftIds2 = wares.stream().map(Ware::getNftId).collect(Collectors.toList());
//        Map<Long, Map<String, Object>> nftMap = goodsMapper.selectArtsManage(nftIds2);
//        arts.addAll(wares.stream().map(w -> {
//            Map<String, Object> map = nftMap.get(w.getNftId());
//            return ArtsManage.builder()
//                    .id(w.getId().toString())
//                    .name(map.get("name").toString())
//                    .uploaderId(map.get("user_id").toString())
//                    .uploaderName(map.get("username").toString())
//                    .attribute(map.get("attribute").toString())
//                    .right(w.getRight())
//                    .status(w.getStatus())
//                    .saleMethod(w.getSaleMethod())
//                    .price(w.getPrice())
//                    .resell(w.getResell())
//                    .edition(w.getEdition())
//                    .build();
//        }).collect(Collectors.toList()));
//        return arts;
        if (userId == null) {
            userId = getUserId();
        }
        List<UserWare> userWares = userWareMapper.selectList(new QueryWrapper<UserWare>().eq("user_id", userId));
        List<Long> wareIds = userWares.stream().map(UserWare::getWareId).collect(Collectors.toList());
        List<Ware> wares = wareMapper.selectList(new QueryWrapper<Ware>().in("id", wareIds));
        List<Long> nftIds = userWares.stream().map(UserWare::getNftId).collect(Collectors.toList());
        List<Nft> nfts = nftMapper.selectList(new QueryWrapper<Nft>().in("id", nftIds));
        Map<Long, Nft> nftMap = new HashMap<>();
        nfts.forEach(n -> nftMap.put(n.getId(), n));
        List<ArtsManage> arts = wares.stream().map(w -> {
            Nft nft = nftMap.get(w.getNftId());
            return ArtsManage.builder()
                    .id(w.getId().toString())
                    .name(nft.getName())
                    .uploaderId(nft.getUserId().toString())
                    .uploaderName(nft.getUsername())
                    .attribute(nft.getAttribute())
                    .right(w.getRight())
                    .status(w.getStatus())
                    .saleMethod(w.getSaleMethod())
                    .price(w.getPrice())
                    .resell(w.getResell())
                    .edition(w.getEdition())
                    .build();
        }).collect(Collectors.toList());
        Page<ArtsManage> page = PageHelper.startPage(pageNum, pageSize);
        PageInfo<ArtsManage> pageInfo = PageInfo.of(page);
        pageInfo.setList(arts);
        return pageInfo;
    }


    @Override
    public LoginInfo passwordLogin(String username, String password) {
        Assert.notNull(username, "用户名不能为空");
        Assert.notNull(password, "密码不能为空");
        User user = userMapper.selectOne(new QueryWrapper<User>().eq("name", username).eq("passwd", password));
        Assert.notNull(user, "登录失败");
        return LoginInfo.builder()
                .userId(user.getId().toString())
                .token(jwtUtil.createToken(user.getId()))
                .roles(loginService.getRolesByUserId(user.getId()))
                .build();
    }

    private long getUserId() {
        String token = SecurityUtils.getSubject().getPrincipal().toString();
        Long userId = JWTUtil.getUserId(token);
        org.springframework.util.Assert.notNull(userId, "service 获取不到用户 id");
        return userId;
    }
}
